/****************************************************************************
**
** Copyright (C) 2010 Tieto Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Tieto Corporation (info.ostrava@tieto.com)
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial Usage
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Tieto.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Tieto gives you certain additional
** rights.  These rights are described in the Tieto Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3.0 as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU General Public License version 3.0 requirements will be
** met: http://www.gnu.org/copyleft/gpl.html.
**
** If you have questions regarding the use of this file, please contact
** Tieto at info.ostrava@tieto.com.
** $QT_END_LICENSE$
**
****************************************************************************/

#include <QtGui/QImage>
#include <QtGui/QColor>
#include <QtGui/QPainter>
#include <QtDebug>

#include "featureclassificationmodel.h"
#include "imagemodel.h"
#include <math.h>

FeatureClassificationTask::FeatureClassificationTask(FeatureClassificationModel &model, QObject *parent)
    : BackgroundTask(QString(tr("Feature classification")), parent),
    _model(model)
{
}

void FeatureClassificationTask::doRun()
{        
    IndexImage image;
    image = _model.indexedAreas();
    _model.reset();
    int width = image.width();
    int height = image.height();;
    progressRangeChanged(0, height);
    for(int row = 0; row < height; row++) {
        for (int col = 0; col < width; col++) {
            int index = image.index(col, row);
            if (index) {
                _model.updateExtremeCoordFeature(col, row, index - 1);
                _model.updateMomentFeature(0, 0, col, row, index - 1, FeatureClassificationModel::EM00);
                _model.updateMomentFeature(1, 0, col, row, index - 1, FeatureClassificationModel::EM10);
                _model.updateMomentFeature(0, 1, col, row, index - 1, FeatureClassificationModel::EM01);
                _model.updatePerimeterFeature(col, row, index - 1);
            }
            if (_isCanceled) {
                break;
            }
        }        
        progressValueChanged(row);
    }

    int rows = _model.rowCount();
    progressRangeChanged(0, rows);
    for (int row = 0; row < rows; row++) {
        _model.updateTODO(row);
        _model.updateMassCenterFeature(row);
        progressValueChanged(row);
    }

    progressRangeChanged(0, height);
    for(int row = 0; row < height; row++) {
        for (int col = 0; col < width; col++) {
            int index = image.index(col, row);
            if (index) {                
                _model.updateMassCenterMomentFeature(1, 1, col, row, index - 1, FeatureClassificationModel::EU11);
                _model.updateMassCenterMomentFeature(2, 0, col, row, index - 1, FeatureClassificationModel::EU20);
                _model.updateMassCenterMomentFeature(0, 2, col, row, index - 1, FeatureClassificationModel::EU02);
                _model.updateImageFeature(col, row, index - 1);
            }
            if (_isCanceled) {
                break;
            }
        }

        progressValueChanged(row);
    }

    progressRangeChanged(0, rows);
    for (int row = 0; row < rows; row++) {
        _model.updateMainMassCenterMomentFeature(row);        
        progressValueChanged(row);
    }
    _model.commit();
}

void FeatureClassificationTask::doCancel()
{

}

FeatureClassificationModel::FeatureClassificationModel(QObject *parent) :
    FeatureModel(parent)
{
    proxyModel = new QStandardItemModel(this);
    _imageModel = new ImageModel();
    reset();
}

QStandardItemModel &FeatureClassificationModel::model()
{
    return *proxyModel;    
}

QStandardItemModel &FeatureClassificationModel::imageModel()
{
    return *_imageModel;
}

void FeatureClassificationModel::reset()
{
    // TODO
    clear();
    proxyModel->clear();
    QStringList headerLabels; // TODO headers
    headerLabels <<
            tr("Image") <<
            tr("TODO") <<
            tr("P") <<
            tr("xleft") <<
            tr("xright") <<
            tr("ytop") <<
            tr("ybottom") <<
            tr("m0,0") <<
            tr("m1,0") <<
            tr("m0,1") <<
            tr("xt") <<
            tr("yt") <<
            tr("u11") <<
            tr("u20") <<
            tr("u02") <<
            tr("umin") <<
            tr("umax") <<
            tr("P");
    proxyModel->setHorizontalHeaderLabels(headerLabels);
}

void FeatureClassificationModel::commit()
{
    while (rowCount()) {
        ItemList items = takeRow(0);
        proxyModel->appendRow(items);
    }
    ItemTable features = externalize();
    emit featuresComputed(features);
}

FeatureClassificationTask* FeatureClassificationModel::featureClassification()
{
    return new FeatureClassificationTask(*this);
}


IndexImage FeatureClassificationModel::indexedAreas()
{
    return qVariantValue<IndexImage>(_imageModel->feature(EIndexedAreas, 0));
}

ColorImage FeatureClassificationModel::indexedAreasImage()
{
    return qVariantValue<ColorImage>(_imageModel->feature(EIndexedAreasImage, 0));
}

void FeatureClassificationModel::setIndexedAreas(const IndexImage &image)
{
    _imageModel->setFeature(EIndexedAreas, 0, qVariantFromValue(image));
}

void FeatureClassificationModel::setIndexedAreasImage(const ColorImage &image)
{
    _imageModel->setFeature(EIndexedAreasImage, 0, qVariantFromValue(image));
}

double FeatureClassificationModel::updateMomentFeature(int p, int q, int x, int y, int row, MomentFeature feature)
{
    double moment = qVariantValue<double>(this->feature(row, feature));
    moment += computeMoment(p, q, x, y);
    setFeature(row, feature, qVariantFromValue(moment));
    return moment;
}

double FeatureClassificationModel::updateMassCenterMomentFeature(int p, int q, int x, int y,  int row, MomentFeature feature)
{

    double xt = qVariantValue<double>(this->feature(row, EXt));
    double yt = qVariantValue<double>(this->feature(row, EYt));
    double moment = updateMomentFeature(p, q, x - xt, y - yt, row, feature);
    return moment;
}

void FeatureClassificationModel::updateMassCenterFeature(int row)
{   
    double m00 = qVariantValue<double>(feature(row, EM00));
    double m10 = qVariantValue<double>(feature(row, EM10));
    double m01 = qVariantValue<double>(feature(row, EM01));
    double xt = (m00) ? m10 / m00 : 0.0;
    double yt = (m00) ? m01 / m00 : 0.0;
    setFeature(row, EXt, qVariantFromValue(xt));
    setFeature(row, EYt, qVariantFromValue(yt));
    ColorImage image = indexedAreasImage();
    if (!image.isNull()) {
        QPainter painter(&image);
        painter.setPen(QColor(Qt::black));
        painter.drawLine(xt - 1, yt, xt + 1, yt);
        painter.drawLine(xt, yt - 1, xt, yt + 1);
        painter.end();
    }
}

void FeatureClassificationModel::updateMainMassCenterMomentFeature(int row)
{
    double u11 = qVariantValue<double>(feature(row, EU11));
    double u20 = qVariantValue<double>(feature(row, EU20));
    double u02 = qVariantValue<double>(feature(row, EU02));
    double umax = computeMainMassCenterMoment(u11, u20, u02, 1.0);
    double umin = computeMainMassCenterMoment(u11, u20, u02, -1.0);
    setFeature(row, EUmax, qVariantFromValue(umax));
    setFeature(row, EUmin, qVariantFromValue(umin));
}

void FeatureClassificationModel::updateExtremeCoordFeature(int x, int y, int row)
{
    updateExtremeCoord(x, row, EXleft, 0);
    updateExtremeCoord(x, row, EXright, 1);
    updateExtremeCoord(y, row, EYtop, 0);
    updateExtremeCoord(y, row, EYbottom, 1);
}

void FeatureClassificationModel::updatePerimeterFeature(int x, int y, int row)
{   
    int perimeter = qVariantValue<int>(this->feature(row, EPerimeter));
    int value = 1;
    bool res11 = isArea(x - 1, y - 1);
    bool res21 = isArea(x - 1, y);
    bool res31 = isArea(x - 1, y + 1);
    bool res12 = isArea(x, y - 1);
    bool res22 = isArea(x, y);
    bool res32 = isArea(x, y + 1);
    bool res13 = isArea(x + 1, y - 1);
    bool res23 = isArea(x + 1, y);
    bool res33 = isArea(x + 1, y + 1);

    if (res22 && (!res11 || !res12 || !res13 || !res21 || !res23 || !res31 || !res32 || !res33)) {
        perimeter++;
        setFeature(row, EPerimeter, qVariantFromValue(perimeter));
        ColorImage image = indexedAreasImage();
        if (!image.isNull()) {
            QPainter painter(&image);
            painter.setPen(QColor(Qt::black));
            painter.drawPoint(x, y);
            painter.end();
        }
    }
}

void FeatureClassificationModel::updateImageFeature(int x, int y, int row)
{
    QVariant varImage = feature(row, EImage);
    int xleft = qVariantValue<int>(feature(row, EXleft));
    int xright = qVariantValue<int>(feature(row, EXright));
    int ytop = qVariantValue<int>(feature(row, EYtop));
    int ybottom = qVariantValue<int>(feature(row, EYbottom));
    QImage image;
    if (varImage.isValid()) {
        image = varImage.value<QImage>();
    }
    else {
        QSize imageSize = QSize(xright - xleft + 1, ybottom - ytop + 1);
        image = ColorImage(imageSize);
    }
    int cx = x - xleft;
    int cy = y - ytop;
    ColorImage indexedAreasImage;
    indexedAreasImage = this->indexedAreasImage();
    if (!indexedAreasImage.isNull()) {
        QRgb pixel = indexedAreasImage.pixel(x, y);
        image.setPixel(cx, cy, pixel);
        setFeature(row, EImage, image);
        QModelIndex index = modelIndex(row, EImage);
        QStandardItem *item = itemFromIndex(index);
        if (item) {
            item->setIcon(QPixmap::fromImage(image)); // TODo
        }
    }
}

void FeatureClassificationModel::updateTODO(int row)
{
    double area = qVariantValue<double>(feature(row, EM00));
    double perimeter = qVariantValue<double>(feature(row, EPerimeter));
    double TODO = (area) ? pow(perimeter, 2) / area : 0.0;
    setFeature(row, ETODO, qVariantFromValue(TODO));
}

ItemTable FeatureClassificationModel::externalize()
{
    ItemTable itemTable;
    QVector<int> cols; // TODO
    cols << EImage << ETODO;
    for (int col = 0; col < cols.size(); col++) {
        ItemList items;
        for(int row = 0; row < proxyModel->rowCount(); row++) {
            QStandardItem* item = proxyModel->item(row, cols[col]);
            if (item) {
                items.append(item->clone());
            }
            else {
                items.append(0);
            }
        }
        itemTable.append(items);
    }
    return itemTable;
}

double FeatureClassificationModel::computeMoment(int p, int q, int x, int y)
{
    double moment = pow(x, p) * pow(y, q);
    return moment;
}

double FeatureClassificationModel::computeMainMassCenterMoment(double u11, double u20, double u02, double sign)
{
    double moment = 0.0;    
    double square = (4.0 * pow(u11, 2)) - pow((u20 - u02), 2);
    moment = (0.5 * (u20 + u02)) + (sign * 0.5 * sqrt(square));
    return moment;
}

bool FeatureClassificationModel::isArea(int x, int y)
{
    IndexImage image = indexedAreas();
    if ((0 <= x) && (x < image.width()) && (0 <= y) && (y < image.height())) {
        int index = image.index(x, y);
        return index;
    }
    return false;
}

void FeatureClassificationModel::updateExtremeCoord(int coord, int row, FeatureID feature, int extreme)
{
    QVariant variant = this->feature(row, feature);
    int newExtreme = coord;
    if (variant.isValid()) {
        int currentExtreme = variant.toInt();
        if (extreme) {
            if (newExtreme > currentExtreme) {
                setFeature(row, feature, QVariant(newExtreme));
            }
        }
        else {
            if (newExtreme < currentExtreme) {
                setFeature(row, feature, QVariant(newExtreme));
            }
        }
    }
    else {
        setFeature(row, feature, newExtreme);
    }
}
